#include <task.h>
#include <malloc.h>
#include <log.h>
#include <assert.h>
#include <core/vmspace.h>
#include <vfs/vfs.h>

slot_t slot_alloc(process_t *process)
{
	assert(process);

	unsigned int size = process->slot_table.slots_size;
	kobject_t **slots = process->slot_table.slots;

	for (int i = 0; i < size; i++)
	{
		if (slots[i] == NULL)
		{
			return i;
		}
	}

	return -1;
}

kobject_t *slot_get(process_t *process, slot_t slot)
{
	unsigned int size = process->slot_table.slots_size;
	kobject_t **slots = process->slot_table.slots;

	if (size > slot)
	{
		return slots[slot];
	}
	return NULL;
}

void _slot_install(process_t *process, slot_t slot_id, kobject_t *obj)
{
	assert(process && obj);

	process->slot_table.slots[slot_id] = obj;
}

slot_t _slot_alloc_install(process_t *process, kobject_t *obj)
{
	assert(process && obj);

	slot_t slot = slot_alloc(process);

	if (slot < 0)
	{
		return -1;
	}
	slot_install(process, slot, obj);
	return slot;
}

void slot_uninstall(process_t *process, slot_t slot_id)
{
	assert(process);
	kobject_t *obj = process->slot_table.slots[slot_id];
	process->slot_table.slots[slot_id] = NULL;
	delete (obj);
}

slot_t slot_copy(process_t *src, process_t *dest, slot_t slot)
{
	kobject_t *obj = slot_get(src, slot);
	slot_t ret = slot_alloc_install(dest, obj);
	return ret;
}

slot_t sys_slot_copy(slot_t dest, slot_t slot)
{
	kobject_t *obj = slot_get(process_self(), slot);
	process_t *pdest = dynamic_cast(process_t)(slot_get(process_self(), dest));

	if (pdest)
	{
		slot_t ret = slot_alloc_install(pdest, obj);
		return ret;
	}
	return -1;
}

slot_t sys_process_create(void)
{
	process_t *new_process = new (process_t);
	slot_t slot = slot_alloc_install(process_self(), new_process);
	return slot;
}

void sys_process_exit(s64_t retcode)
{
	LOGI();
	// send dying signal to all threads in this process

	// exit this thread.
	thread_exit(thread_self());
}

class_impl(process_t, kobject_t){};

constructor(process_t)
{
	init_list_head(&this->thread_list);
	this->slot_table.slots_size = MAX_SLOTS_SIZE;
	this->slot_table.slots = calloc(1, MAX_SLOTS_SIZE * sizeof(kobject_t *));
	this->vmspace = new (vmspace_t);

	slot_alloc_install(this, this);
	slot_alloc_install(this, this->vmspace);
}

vaddr_t prepare_env(paddr_t paddr, vaddr_t vaddr)
{
	const char *env = "{}"; // json
	int len = strlen(env) + 1;

	vaddr -= len;
	paddr -= len;

	memcpy((void *)(paddr), (void *)(env), len);
	LOGD("user sp:", $(vaddr));
	return vaddr;
}

static paddr_t load_binary(process_t *process, vmspace_t *vmspace, const char *file, vaddr_t addr)
{
	int fd = vfs_open(file, O_RDONLY, 0);

	if (fd < 0)
		return 0;

	struct vfs_stat_t st;
	vfs_fstat(fd, &st);
	int page_num = (st.st_size + PAGE_SIZE - 1) >> PAGE_SHIFT;
	vmobject_t *bin_vmo = vmo_create(page_num * PAGE_SIZE, VMO_DATA);
	void *buf = bin_vmo->start;

	LOGI("size:" $((int)st.st_size));
	LOGI("buf:" $(buf));

	vfs_read(fd, buf, st.st_size);

	slot_alloc_install(process, bin_vmo);

	bool_t ret = vmspace_map_range_user(vmspace, addr, page_num * PAGE_SIZE, PROT_WRITE | PROT_READ | PROT_EXEC, 0, bin_vmo);
	LOGD("User Space: [" $(addr) " ~ " $(addr + page_num * PAGE_SIZE - 1) "]");

	return (paddr_t)addr;
}

process_t *launch_user_init(process_t *parent, const char *filename)
{
	LOGD("launch [" $(filename) "]");

	process_t *process = new (process_t);
	vmspace_t *vmspace = process->vmspace;
	thread_t *current = thread_self();

	slot_alloc_install(parent, process);

	paddr_t pc = load_binary(process, vmspace, filename, 0x20000000UL);

	if (!pc)
	{
		LOGE("Failed to load " $(filename));
		return NULL;
	}

	int pagecount = 16;
	size_t stack_size = PGSIZE * pagecount;
	vaddr_t ustack = vmspace->user_end + 1 - stack_size;
	LOGI("ustack:" $(ustack));

	vmobject_t *ustack_vmo = vmo_create(PGSIZE * pagecount, VMO_DATA);
	slot_alloc_install(process, ustack_vmo);

	bool_t ret = vmspace_map_range_user(vmspace, ustack, stack_size, PROT_WRITE | PROT_READ, 0, ustack_vmo);

	vaddr_t sp = prepare_env(ustack_vmo->start + ustack_vmo->size, ustack + +ustack_vmo->size);
	thread_t *thread = thread_create(process, (void *)sp, (void *)pc, NULL);
	thread->name = filename;

	LOGD("thread " $(thread) "[" $(filename) "]");
	thread_resume(thread);

	return process;
}

static process_t *root;
extern void do_user_block_init(const char *dev);

void do_user_init()
{
	root = new (process_t);

	do_user_block_init("virtio-block");

	vfs_mount("virtio-block", "/", "cpio", MOUNT_RO);
	// launch_user_init(root, "/idle");
	launch_user_init(root, "/bin/vfs_server");
	launch_user_init(root, "/bin/wm");
	launch_user_init(root, "/bin/dock");

	LOGD("exit");
}
